import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import datetime as dt
import yfinance as yf
plt.style.use("ggplot")
import statsmodels.api as smCapital Assets Pricing Model
Unsystematic risks can be diversified. Systematic risks can’t be diversified, \(\text{CAPM}\) measures this risk with \(\beta\).
\[ \begin{aligned} E(R_i)= & R_f+\beta_i[E(R_m)-R_f] \end{aligned} \]
\[ \begin{array} E\left(R_i\right) & =\text { capital asset expected return, e.g. a single stock or a portfolio } \\ R_f & =\text { risk-free return, e.g. 10y treasury bond} \\ \beta_i & =\text { sensitivity } \\ E\left(R_m\right) & =\text { expected return of the market, e.g. S&P500 } \end{array} \]
\(\beta_i\) can be calculated by OLS formula, i.e. \[ \beta= \frac{\text{Cov}(R_i, R_m)}{\text{Var}(R_m)} \] it tells how risky your portfolio is relative to the market, it only measures the systematic risk.
The portfolio \(\beta\) will be a weighted \(\beta\) \[ \beta = \sum_{i=1}^n w_i\beta_i \]
The model can be rewritten as \[\begin{aligned} E(R_i)-R_f= \alpha_i+\beta_i[E(R_m)-R_f] \end{aligned}\]to explicitly estimate \(\alpha\) as a constant. It characterizes the excess rate of return that
\[ \alpha_i= E(R_i)-\{R_f+\beta_i[E(R_m)-R_f]\} \] Usually the classic CAPM assumes \(\alpha=0\)
In short, \(\beta\) measures systematic risks, \(\alpha\) measure excess returns.
Implementation of CAPM
class CAPM:
def __init__(self, stocks, start_date, end_date):
self.stocks = stocks
self.start_date = start_date
self.end_date = end_date
def download_data(self):
stock_data = {}
for stock in self.stocks:
ticker = yf.Ticker(stock)
stock_data[stock] = ticker.history(
start=self.start_date, end=self.end_date
)["Close"]
return pd.DataFrame(stock_data)
def resample_month(self):
# montly return gives a better normally distributed results
stock_data = (
self.download_data()
) # without this, 'stock_data' will be called before assignment
stock_data = stock_data.resample("M").last()
self.data = pd.DataFrame(
{
"s_adjclose": stock_data[self.stocks[0]],
"m_adjclose": stock_data[self.stocks[1]],
}
)
self.data[["s_logreturn", "m_logreturn"]] = np.log(self.data) - np.log(
self.data.shift()
)
self.data = self.data.dropna()
return self.data
def calculate_beta(self):
cov_matrix = np.cov(self.data["s_logreturn"], self.data["m_logreturn"])
self.beta = cov_matrix[0, 1] / np.var(self.data["m_logreturn"])
print(self.beta)
return self.beta
def lin_reg(self):
R_F = 0.05
month_in_year = 12
X = self.data["m_logreturn"] - R_F
Y = self.data["s_logreturn"] - R_F
X = sm.add_constant(X) # adding a constant
self.results = sm.OLS(Y, X).fit()
alpha, beta = self.results.params[0], self.results.params[1]
expected_return = R_F + beta * (
self.data["m_logreturn"].mean() * month_in_year - R_F
)
self.plot_regression()
print("alpha: {}".format(alpha))
print("beta: {}".format(beta))
return alpha, beta, expected_return
def plot_regression(self):
fig, ax = plt.subplots(figsize=(6, 6))
ax.scatter(
self.data["m_logreturn"], self.data["s_logreturn"], c="CornflowerBlue"
)
ax.plot(self.data["m_logreturn"], self.results.fittedvalues)
ax.set_title("Capital Assets Pricing Model: {}".format(stocks[0]))
ax.set_ylabel("Expected Stock Return")
ax.set_xlabel("Expected Market Return")
plt.show()
if __name__ == "__main__":
stocks = ["LULU", "^GSPC"]
capm = CAPM(stocks, start_date="2010-01-01", end_date=dt.datetime.today())
data = capm.download_data()
data_month = capm.resample_month()
beta_calculated = capm.calculate_beta()
alpha, beta, exp_ret = capm.lin_reg()1.2162665097364047
/tmp/ipykernel_3463/4094164405.py:21: FutureWarning:
'M' is deprecated and will be removed in a future version, please use 'ME' instead.
/tmp/ipykernel_3463/4094164405.py:48: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`

alpha: 0.014293598162767422
beta: 1.2096920961702622